/*
 * Nextcloud - Android Client
 *
 * SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
 * SPDX-FileCopyrightText: 2020 Tobias Kaminsky <tobias@kaminsky.me>
 * SPDX-FileCopyrightText: 2020 Nextcloud GmbH
 * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
 */
package com.owncloud.android.ui.dialog

import android.Manifest
import android.accounts.Account
import android.accounts.AccountManager
import android.app.Dialog
import android.content.Intent
import android.net.http.SslCertificate
import android.net.http.SslError
import android.os.Looper
import android.view.ViewGroup
import android.webkit.SslErrorHandler
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContract
import androidx.fragment.app.DialogFragment
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.launchActivity
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.rule.GrantPermissionRule
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.nextcloud.android.common.ui.color.ColorUtil
import com.nextcloud.android.lib.resources.profile.Action
import com.nextcloud.android.lib.resources.profile.HoverCard
import com.nextcloud.client.account.RegisteredUser
import com.nextcloud.client.account.Server
import com.nextcloud.client.device.DeviceInfo
import com.nextcloud.client.documentscan.AppScanOptionalFeature
import com.nextcloud.ui.ChooseAccountDialogFragment
import com.nextcloud.ui.ChooseAccountDialogFragment.Companion.newInstance
import com.nextcloud.ui.SetOnlineStatusBottomSheet
import com.nextcloud.ui.fileactions.FileActionsBottomSheet.Companion.newInstance
import com.nextcloud.utils.EditorUtils
import com.owncloud.android.AbstractIT
import com.owncloud.android.MainApp
import com.owncloud.android.R
import com.owncloud.android.authentication.EnforcedServer
import com.owncloud.android.datamodel.ArbitraryDataProvider
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.common.Creator
import com.owncloud.android.lib.common.DirectEditing
import com.owncloud.android.lib.common.Editor
import com.owncloud.android.lib.common.OwnCloudAccount
import com.owncloud.android.lib.common.accounts.AccountTypeUtils
import com.owncloud.android.lib.common.accounts.AccountUtils
import com.owncloud.android.lib.resources.status.CapabilityBooleanType
import com.owncloud.android.lib.resources.status.OCCapability
import com.owncloud.android.lib.resources.status.OwnCloudVersion
import com.owncloud.android.lib.resources.users.Status
import com.owncloud.android.lib.resources.users.StatusType
import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.ui.dialog.LoadingDialog.Companion.newInstance
import com.owncloud.android.ui.dialog.RenameFileDialogFragment.Companion.newInstance
import com.owncloud.android.ui.dialog.SharePasswordDialogFragment.Companion.newInstance
import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.Companion.newInstanceForEmptySslError
import com.owncloud.android.ui.fragment.OCFileListBottomSheetActions
import com.owncloud.android.ui.fragment.OCFileListBottomSheetDialog
import com.owncloud.android.ui.fragment.ProfileBottomSheetDialog
import com.owncloud.android.utils.MimeTypeUtil
import com.owncloud.android.utils.ScreenshotTest
import com.owncloud.android.utils.theme.CapabilityUtils
import com.owncloud.android.utils.theme.ViewThemeUtils
import io.mockk.mockk
import org.junit.After
import org.junit.Rule
import org.junit.Test
import java.net.URI
import java.util.function.Supplier

@Suppress("TooManyFunctions")
class DialogFragmentIT : AbstractIT() {
    private val testClassName = "com.owncloud.android.ui.dialog.DialogFragmentIT"
    private val serverUrl = "https://nextcloud.localhost"

    @get:Rule
    val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.POST_NOTIFICATIONS
    )

    @After
    fun quitLooperIfNeeded() {
        Looper.myLooper()?.quitSafely()
    }

    @Test
    @ScreenshotTest
    fun testRenameFileDialog() {
        if (Looper.myLooper() == null) {
            Looper.prepare()
        }

        newInstance(
            OCFile("/Test/"),
            OCFile("/")
        ).run {
            showDialog(this)
        }
    }

    @Test
    @ScreenshotTest
    fun testLoadingDialog() {
        newInstance("Wait…").run {
            showDialog(this)
        }
    }

    @Test
    @ScreenshotTest
    fun testConfirmationDialogWithOneAction() {
        ConfirmationDialogFragment.newInstance(
            R.string.upload_list_empty_text_auto_upload,
            arrayOf(),
            R.string.filedetails_sync_file,
            R.drawable.ic_warning,
            -1,
            -1,
            -1
        ).run {
            showDialog(this)
        }
    }

    @Test
    @ScreenshotTest
    fun testConfirmationDialogWithTwoAction() {
        ConfirmationDialogFragment.newInstance(
            R.string.upload_list_empty_text_auto_upload,
            arrayOf(),
            R.string.filedetails_sync_file,
            R.drawable.ic_warning,
            R.string.common_cancel,
            -1,
            -1
        ).run {
            showDialog(this)
        }
    }

    @Test
    @ScreenshotTest
    fun testConfirmationDialogWithThreeAction() {
        ConfirmationDialogFragment.newInstance(
            R.string.upload_list_empty_text_auto_upload,
            arrayOf(),
            R.string.filedetails_sync_file,
            R.drawable.ic_warning,
            R.string.common_cancel,
            R.string.common_confirm,
            -1
        ).run {
            showDialog(this)
        }
    }

    @Test
    @ScreenshotTest
    fun testConfirmationDialogWithThreeActionRTL() {
        enableRTL()
        ConfirmationDialogFragment.newInstance(
            R.string.upload_list_empty_text_auto_upload,
            arrayOf(),
            -1,
            R.drawable.ic_warning,
            R.string.common_cancel,
            R.string.common_confirm,
            -1
        ).run {
            showDialog(this)
            resetLocale()
        }
    }

    @Test
    @ScreenshotTest
    fun testRemoveFileDialog() {
        RemoveFilesDialogFragment.newInstance(OCFile("/Test.md")).run {
            showDialog(this)
        }
    }

    @Test
    @ScreenshotTest
    fun testRemoveFilesDialog() {
        val toDelete = ArrayList<OCFile>().apply {
            add(OCFile("/Test.md"))
            add(OCFile("/Document.odt"))
        }

        val dialog: RemoveFilesDialogFragment = RemoveFilesDialogFragment.newInstance(toDelete)
        showDialog(dialog)
    }

    @Test
    @ScreenshotTest
    fun testRemoveFolderDialog() {
        val dialog = RemoveFilesDialogFragment.newInstance(OCFile("/Folder/"))
        showDialog(dialog)
    }

    @Test
    @ScreenshotTest
    fun testRemoveFoldersDialog() {
        val toDelete = ArrayList<OCFile>()
        toDelete.add(OCFile("/Folder/"))
        toDelete.add(OCFile("/Documents/"))

        val dialog: RemoveFilesDialogFragment = RemoveFilesDialogFragment.newInstance(toDelete)
        showDialog(dialog)
    }

    @Test
    @ScreenshotTest
    fun testNewFolderDialog() {
        if (Looper.myLooper() == null) {
            Looper.prepare()
        }
        val sut = CreateFolderDialogFragment.newInstance(OCFile("/"))
        showDialog(sut)
    }

    @Test
    @ScreenshotTest
    fun testEnforcedPasswordDialog() {
        if (Looper.myLooper() == null) {
            Looper.prepare()
        }
        val sut = newInstance(OCFile("/"), true, false)
        showDialog(sut)
    }

    @Test
    @ScreenshotTest
    fun testOptionalPasswordDialog() {
        if (Looper.myLooper() == null) {
            Looper.prepare()
        }
        val sut = newInstance(OCFile("/"), true, true)
        showDialog(sut)
    }

    @Test
    @ScreenshotTest
    fun testAccountChooserDialog() {
        val intent = Intent(targetContext, FileDisplayActivity::class.java)
        ActivityScenario.launch<FileDisplayActivity>(intent).use { scenario ->
            var sut: ChooseAccountDialogFragment? = null

            scenario.onActivity { activity: FileDisplayActivity ->

                val userAccountManager = activity.userAccountManager
                val accountManager = AccountManager.get(targetContext)
                for (account in accountManager.getAccountsByType(MainApp.getAccountType(targetContext))) {
                    accountManager.removeAccountExplicitly(account)
                }

                val newAccount = Account("test@https://nextcloud.localhost", MainApp.getAccountType(targetContext))
                accountManager.addAccountExplicitly(newAccount, "password", null)
                accountManager.setUserData(newAccount, AccountUtils.Constants.KEY_OC_BASE_URL, serverUrl)
                accountManager.setUserData(newAccount, AccountUtils.Constants.KEY_USER_ID, "test")
                accountManager.setAuthToken(
                    newAccount,
                    AccountTypeUtils.getAuthTokenTypePass(newAccount.type),
                    "password"
                )
                val newUser = userAccountManager.getUser(newAccount.name)
                    .orElseThrow(Supplier { RuntimeException() })
                userAccountManager.setCurrentOwnCloudAccount(newAccount.name)

                val newAccount2 = Account("user1@nextcloud.localhost", MainApp.getAccountType(targetContext))
                accountManager.addAccountExplicitly(newAccount2, "password", null)
                accountManager.setUserData(newAccount2, AccountUtils.Constants.KEY_OC_BASE_URL, serverUrl)
                accountManager.setUserData(newAccount2, AccountUtils.Constants.KEY_USER_ID, "user1")
                accountManager.setUserData(newAccount2, AccountUtils.Constants.KEY_OC_VERSION, "20.0.0")
                accountManager.setAuthToken(
                    newAccount2,
                    AccountTypeUtils.getAuthTokenTypePass(newAccount.type),
                    "password"
                )

                val fileDataStorageManager = FileDataStorageManager(
                    newUser,
                    targetContext.contentResolver
                )

                val capability = OCCapability().apply {
                    userStatus = CapabilityBooleanType.TRUE
                    userStatusSupportsEmoji = CapabilityBooleanType.TRUE
                }
                fileDataStorageManager.saveCapabilities(capability)

                sut = newInstance(
                    RegisteredUser(
                        newAccount,
                        OwnCloudAccount(newAccount, targetContext),
                        Server(URI.create(serverUrl), OwnCloudVersion.nextcloud_20)
                    )
                )

                sut.show(activity.supportFragmentManager, null)
            }

            val dialogInstance = waitForDialog(sut!!) ?: throw IllegalStateException("Dialog was not created")

            scenario.onActivity {
                val viewGroup = dialogInstance.window?.findViewById<ViewGroup>(android.R.id.content)
                if (viewGroup != null) {
                    hideCursors(viewGroup)
                }

                sut.setStatus(
                    Status(
                        StatusType.DND,
                        "Busy fixing 🐛…",
                        "",
                        -1
                    ),
                    targetContext
                )
                screenshot(sut, "dnd")

                sut.setStatus(
                    Status(
                        StatusType.ONLINE,
                        "",
                        "",
                        -1
                    ),
                    targetContext
                )
                screenshot(sut, "online")

                sut.setStatus(
                    Status(
                        StatusType.ONLINE,
                        "Let's have some fun",
                        "🎉",
                        -1
                    ),
                    targetContext
                )
                screenshot(sut, "fun")

                sut.setStatus(
                    Status(StatusType.OFFLINE, "", "", -1),
                    targetContext
                )
                screenshot(sut, "offline")

                sut.setStatus(
                    Status(StatusType.AWAY, "Vacation", "🌴", -1),
                    targetContext
                )
                screenshot(sut, "away")
            }
        }
    }

    @Test
    @ScreenshotTest
    @Throws(AccountUtils.AccountNotFoundException::class)
    fun testAccountChooserDialogWithStatusDisabled() {
        val accountManager = AccountManager.get(targetContext)
        for (account in accountManager.accounts) {
            accountManager.removeAccountExplicitly(account)
        }

        val newAccount = Account("test@https://nextcloud.localhost", MainApp.getAccountType(targetContext))
        accountManager.addAccountExplicitly(newAccount, "password", null)
        accountManager.setUserData(newAccount, AccountUtils.Constants.KEY_OC_BASE_URL, serverUrl)
        accountManager.setUserData(newAccount, AccountUtils.Constants.KEY_USER_ID, "test")
        accountManager.setAuthToken(newAccount, AccountTypeUtils.getAuthTokenTypePass(newAccount.type), "password")

        launchActivity<FileDisplayActivity>().use { scenario ->
            var sut: ChooseAccountDialogFragment? = null
            scenario.onActivity { fda ->
                val userAccountManager = fda.userAccountManager
                val newUser = userAccountManager.getUser(newAccount.name).get()
                val fileDataStorageManager = FileDataStorageManager(
                    newUser,
                    targetContext.contentResolver
                )

                val capability = OCCapability().apply {
                    userStatus = CapabilityBooleanType.FALSE
                }

                fileDataStorageManager.saveCapabilities(capability)

                sut = newInstance(
                    RegisteredUser(
                        newAccount,
                        OwnCloudAccount(newAccount, targetContext),
                        Server(
                            URI.create(serverUrl),
                            OwnCloudVersion.nextcloud_20
                        )
                    )
                )

                sut.show(fda.supportFragmentManager, null)
            }

            waitForDialog(sut!!) ?: throw IllegalStateException("Dialog was not created")

            onView(isRoot()).check(matches(isDisplayed()))
        }
    }

    @Test
    @ScreenshotTest
    fun testBottomSheet() {
        if (Looper.myLooper() == null) {
            Looper.prepare()
        }

        val action: OCFileListBottomSheetActions = object : OCFileListBottomSheetActions {
            override fun createFolder() = Unit
            override fun uploadFromApp() = Unit
            override fun uploadFiles() = Unit
            override fun newDocument() = Unit
            override fun newSpreadsheet() = Unit
            override fun newPresentation() = Unit
            override fun directCameraUpload() = Unit
            override fun scanDocUpload() = Unit
            override fun showTemplate(creator: Creator?, headline: String?) = Unit
            override fun createRichWorkspace() = Unit
        }

        val info = DeviceInfo()
        val ocFile = OCFile("/test.md").apply {
            remoteId = "00000001"
        }

        val intent = Intent(targetContext, FileDisplayActivity::class.java)

        launchActivity<FileDisplayActivity>(intent).use { scenario ->
            var sut: OCFileListBottomSheetDialog? = null
            scenario.onActivity { fda ->
                var directEditing = DirectEditing()
                val creators = directEditing.creators.toMutableMap()
                val editors = directEditing.editors.toMutableMap()

                creators["1"] = Creator(
                    "1",
                    "text",
                    "text file",
                    ".md",
                    "application/octet-stream",
                    false
                )

                creators["2"] = Creator(
                    "2",
                    "md",
                    "markdown file",
                    ".md",
                    "application/octet-stream",
                    false
                )

                editors["text"] = Editor(
                    "1",
                    "Text",
                    ArrayList(mutableListOf(MimeTypeUtil.MIMETYPE_TEXT_MARKDOWN)),
                    ArrayList(),
                    false
                )

                directEditing = DirectEditing(editors, creators)
                val json = Gson().toJson(directEditing)

                ArbitraryDataProviderImpl(targetContext).storeOrUpdateKeyValue(
                    user.accountName,
                    ArbitraryDataProvider.DIRECT_EDITING,
                    json
                )

                val capability = fda.capabilities.apply {
                    richDocuments = CapabilityBooleanType.TRUE
                    richDocumentsDirectEditing = CapabilityBooleanType.TRUE
                    richDocumentsTemplatesAvailable = CapabilityBooleanType.TRUE
                    accountName = user.accountName
                }
                CapabilityUtils.updateCapability(capability)

                val appScanOptionalFeature: AppScanOptionalFeature = object : AppScanOptionalFeature() {
                    override fun getScanContract(): ActivityResultContract<Unit, String?> =
                        throw UnsupportedOperationException("Document scan is not available")
                }

                val materialSchemesProvider = getMaterialSchemesProvider()
                val viewThemeUtils = ViewThemeUtils(
                    materialSchemesProvider.getMaterialSchemesForCurrentUser(),
                    ColorUtil(targetContext)
                )

                val editorUtils = EditorUtils(ArbitraryDataProviderImpl(targetContext))

                sut = OCFileListBottomSheetDialog(
                    fda,
                    action,
                    info,
                    user,
                    ocFile,
                    fda.themeUtils,
                    viewThemeUtils,
                    editorUtils,
                    appScanOptionalFeature
                )

                sut.show()
            }

            sut!!.behavior.setState(BottomSheetBehavior.STATE_EXPANDED)
            val viewGroup = sut.window?.findViewById<ViewGroup>(android.R.id.content) ?: return
            hideCursors(viewGroup)

            val screenShotName = createName(testClassName + "_" + "testBottomSheet", "")
            onView(isRoot()).check(matches(isDisplayed()))

            screenshotViaName(sut.window?.decorView, screenShotName)
        }
    }

    @Test
    @ScreenshotTest
    fun testOnlineStatusBottomSheet() {
        if (Looper.myLooper() == null) {
            Looper.prepare()
        }

        val intent = Intent(targetContext, FileDisplayActivity::class.java)

        launchActivity<FileDisplayActivity>(intent).use { scenario ->
            var sut: SetOnlineStatusBottomSheet? = null
            scenario.onActivity { fda ->
                sut = SetOnlineStatusBottomSheet(
                    Status(StatusType.DND, "Focus time", "\uD83E\uDD13", -1)
                )
                sut.show(fda.supportFragmentManager, "set_online_status")
            }

            val screenShotName = createName(testClassName + "_" + "testOnlineStatusBottomSheet", "")
            onView(isRoot()).check(matches(isDisplayed()))

            scenario.onActivity {
                screenshotViaName(sut!!.view, screenShotName)
            }
        }
    }

    @Test
    @ScreenshotTest
    fun testProfileBottomSheet() {
        if (Looper.myLooper() == null) {
            Looper.prepare()
        }

        val actions: MutableList<Action> = ArrayList()
        actions.add(
            Action(
                "profile",
                "View profile",
                "https://dev.nextcloud.com/core/img/actions/profile.svg",
                "https://dev.nextcloud.com/index.php/u/christine"
            )
        )
        actions.add(
            Action(
                "core",
                "christine.scott@nextcloud.com",
                "https://dev.nextcloud.com/core/img/actions/mail.svg",
                "mailto:christine.scott@nextcloud.com"
            )
        )

        actions.add(
            Action(
                "spreed",
                "Talk to Christine",
                "https://dev.nextcloud.com/apps/spreed/img/app-dark.svg",
                "https://dev.nextcloud.com/apps/spreed/?callUser=christine"
            )
        )

        val hoverCard = HoverCard("christine", "Christine Scott", actions)
        val intent = Intent(targetContext, FileDisplayActivity::class.java)

        launchActivity<FileDisplayActivity>(intent).use { scenario ->
            var sut: ProfileBottomSheetDialog? = null
            scenario.onActivity { fda ->
                sut = ProfileBottomSheetDialog(
                    fda,
                    user,
                    hoverCard,
                    fda.viewThemeUtils
                )
                sut.show()
            }

            val screenShotName = createName(testClassName + "_" + "testProfileBottomSheet", "")
            onView(isRoot()).check(matches(isDisplayed()))

            scenario.onActivity {
                screenshotViaName(sut!!.window?.decorView, screenShotName)
            }
        }
    }

    @Test
    @ScreenshotTest
    fun testSslUntrustedCertDialog() {
        if (Looper.myLooper() == null) {
            Looper.prepare()
        }

        val certificate = SslCertificate("foo", "bar", "2022/01/10", "2022/01/30")
        val sslError = SslError(SslError.SSL_UNTRUSTED, certificate)

        val handler = mockk<SslErrorHandler>(relaxed = true)

        newInstanceForEmptySslError(sslError, handler).run {
            showDialog(this)
        }
    }

    @Test
    @ScreenshotTest
    fun testStoragePermissionDialog() {
        if (Looper.myLooper() == null) {
            Looper.prepare()
        }

        val dialog = StoragePermissionDialogFragment()
        showDialog(dialog)
    }

    @Test
    @ScreenshotTest
    fun testFileActionsBottomSheet() {
        if (Looper.myLooper() == null) {
            Looper.prepare()
        }

        val ocFile = OCFile("/test.md").apply {
            remoteId = "0001"
        }

        newInstance(ocFile, false).run {
            showDialog(this)
        }
    }

    private fun showDialog(dialog: DialogFragment) {
        launchActivity<FileDisplayActivity>().use { scenario ->
            scenario.onActivity { sut ->
                dialog.show(sut.supportFragmentManager, null)
            }

            val dialogInstance = waitForDialog(dialog) ?: throw IllegalStateException("Dialog was not created")

            scenario.onActivity {
                val viewGroup = dialogInstance.window?.findViewById<ViewGroup>(android.R.id.content)
                if (viewGroup != null) {
                    hideCursors(viewGroup)
                }
                screenshot(dialogInstance.window?.decorView)
            }

            onView(isRoot()).check(matches(isDisplayed()))
        }
    }

    private fun waitForDialog(dialogFragment: DialogFragment, timeoutMs: Long = 5000): Dialog? {
        val start = System.currentTimeMillis()
        while (System.currentTimeMillis() - start < timeoutMs) {
            if (dialogFragment.isAdded && dialogFragment.dialog != null) {
                return dialogFragment.dialog
            }
            Thread.sleep(50)
        }
        return null
    }

    private fun hideCursors(viewGroup: ViewGroup) {
        for (i in 0..<viewGroup.childCount) {
            val child = viewGroup.getChildAt(i)

            if (child is ViewGroup) {
                hideCursors(child)
            }

            if (child is TextView) {
                child.isCursorVisible = false
            }
        }
    }

    @Test
    fun testGson() {
        val t = ArrayList<EnforcedServer?>().apply {
            add(EnforcedServer("name", "url"))
            add(EnforcedServer("name2", "url1"))
        }

        val s = Gson().toJson(t)
        val t2 = Gson().fromJson<ArrayList<EnforcedServer>>(
            s,
            object : TypeToken<ArrayList<EnforcedServer?>?>() {
            }.type
        )

        val temp = ArrayList<String?>()
        for (p in t2) {
            temp.add(p.name)
        }
    }
}
